#include "mvc/view/camera.h"

#include <GL/glu.h>
#include <GL/glut.h>

// Used to keep information of the camera position.
struct
{
    double cx;
    double cy;
    double cz;
} c_eye, default_pos;

// Used to keep information of the camera rotations.
struct
{
    double roll;
    double yaw;
    double pitch;
} c_rotations, default_rot;


/**
  * The Camera constructor
  * camera_position - the initial camera position
  * roll - the initial camera rotation about the 'x' axis.
  * yaw - the initial camera rotation about the 'y' axis.
  * pitch - the initial camera rotation about the 'z' axis.
  */
Camera::Camera(const QVector3D &camera_position, double roll, double yaw, double pitch)
{
    default_pos.cx = c_eye.cx = camera_position.x();
    default_pos.cy = c_eye.cy = camera_position.y();
    default_pos.cz = c_eye.cz = camera_position.z();

    default_rot.roll = c_rotations.roll = roll;
    default_rot.yaw = c_rotations.yaw = yaw;
    default_rot.pitch = c_rotations.pitch = pitch;

    c_view = PERSPECTIVE;
}

Camera::~Camera()
{

}

/**
  * Locate the camera using the position and rotation structs
  */
void Camera::locate_camera()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if(c_view == PERSPECTIVE)
        gluPerspective(30.0, 2.0, 1.0, 100000.0);
    else if(c_view == ORTHO)
        glOrtho(-100.0, 100.0, -100.0, 100.0, 0.1, 100.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glTranslatef(c_eye.cx, c_eye.cy, c_eye.cz);

    glRotatef(c_rotations.roll, 1.0, 0.0, 0.0);
    glRotatef(c_rotations.yaw, 0.0, 1.0, 0.0);
    glRotatef(c_rotations.pitch, 0.0, 0.0, 1.0);
}

/**
  * Rotate the camera "dist" amount
  * to - the rotation direction.
  * dist - the amount of rotation
  */
void Camera::rotate_camera(CRotate to, double dist)
{
    switch(to)
    {
        case ROTATE_R:
            c_rotations.pitch += dist;
            break;
        case ROTATE_L:
            c_rotations.pitch -= dist;
            break;
        case ROTATE_U:
            c_rotations.roll += dist;
            break;
        case ROTATE_D:
            c_rotations.roll -= dist;
            break;
    }

    clamp_camera();
    locate_camera();
}

/**
  * Moves the camera 'dist' amount
  * to - the move camera direction
  * dist - the amount to move
  */
void Camera::move_camera(CMove to, double dist)
{
    dist = (to == MOVE_R || to == MOVE_U)? -dist : dist;

    if(to == MOVE_R || to == MOVE_L)
        c_eye.cx += dist;
    else if(to == MOVE_U || to == MOVE_D)
        c_eye.cy += dist;

    locate_camera();
}

/**
  * make a zoom
  * type -  the type of zoom. Increase or decrease zoom
  * zoom -  the amount to zoom
  */
void Camera::zoom(CZoom type, double zoom)
{
    zoom = (type == INCZ)? -zoom : zoom;

    c_eye.cz -= zoom;

    locate_camera();
}

/**
  * locate the camera to the initial position.
  */
void Camera::reset()
{
    c_eye.cx = default_pos.cx;
    c_eye.cy = default_pos.cy;
    c_eye.cz = default_pos.cz;

    c_rotations.roll = default_rot.roll;
    c_rotations.yaw = default_rot.yaw;
    c_rotations.pitch = default_rot.pitch;

    locate_camera();
}

//
// PRIVATE METHODS
//

/**
  * fix camera rotation values.
  * this values can't be greater than 360 and less 0
  */
void Camera::clamp_camera()
{
    if(c_rotations.roll < 0)
        clamp_negative(c_rotations.roll);
    else if(c_rotations.roll > 360)
        clamp_positive(c_rotations.roll);

    if(c_rotations.yaw < 0)
        clamp_negative(c_rotations.yaw);
    else if(c_rotations.yaw > 360)
        clamp_positive(c_rotations.yaw);

    if(c_rotations.pitch < 0)
        clamp_negative(c_rotations.pitch);
    else if(c_rotations.pitch > 360)
        clamp_positive(c_rotations.pitch);
}

/**
  *
  */
void Camera::clamp_positive(double &value)
{
    value = value - 360.0;
}

/**
  *
  */
void Camera::clamp_negative(double &value)
{
    value = 360.0 + value;
}
